﻿namespace NJQuery {
	using System;
	using System.Web.UI;
	using System.ComponentModel;
	using System.Collections.Specialized;
	using System.Globalization;

	/// <summary>
	/// Server control used to generate a <see href="http://jqueryui.com/demos/slider/">jQuery UI Slider widget</see> with a single value.
	/// </summary>
	public class Slider : SliderBase {


		#region Constants: Property defaults

		private const int defaultValue = 0;
		private const SliderFixedRange defaultRange = SliderFixedRange.None;
		private const string defaultValueDisplayControl = "";

		#endregion


		#region Fields

		private string valueDisplayControlClientId;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Slider"/> class.
		/// </summary>
		public Slider() { }

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// A value of "Min" higlights the range from the slider min to the handle. A "Max" value displays the range from the handle to the slider max.
		/// This property maps to the "range" option when the jQuery UI widget is initialized.  To set a value of "true" as supported by the widget, use the SliderRange server control instead.
		/// </summary>
		/// <seealso cref="SliderRange"/>
		[DefaultValue(defaultRange), Category("Appearance")]
		public virtual SliderFixedRange Range {
			get { return ViewState["Range"] as SliderFixedRange? ?? defaultRange; }
			set {
				if(!Enum.IsDefined(typeof(SliderFixedRange), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["Range"] = value;
			}
		}

		/// <summary>
		/// Determines the value of the slider.
		/// This property maps directly to the "value" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultValue), Category("Data")]
		public virtual int Value {
			get { return ViewState["Value"] as int? ?? defaultValue; }
			set { ViewState["Value"] = value; }
		}

		#endregion


		#region Properties: Extended widget functionality

		/// <summary>
		/// Gets or sets reference to a control that can display the value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string ValueDisplayControl {
			get { return ViewState["ValueDisplayControl"] as string ?? defaultValueDisplayControl; }
			set { ViewState["ValueDisplayControl"] = value; }
		}

		#endregion


		#region Properties: Postback

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the <see cref="Value"/> property.
		/// </summary>
		public virtual string ValueClientId {
			get { return string.Format(CultureInfo.InvariantCulture, "__{0}_Value", ClientID); }
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Raises the <see cref="Control.PreRender"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			valueDisplayControlClientId = this.GetClientId(ValueDisplayControl);
		}

		/// <summary>
		/// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A <see cref="HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {
			base.Render(writer);

			ClientScriptManager clientScript = Page.ClientScript;
			clientScript.RegisterForEventValidation(new PostBackOptions(this));

			clientScript.RegisterHiddenField(ValueClientId, Value.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Renders the jQuery "Ready Event" script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderReadyScript(ScriptBuilder script) {
			base.RenderReadyScript(script);

			if(!string.IsNullOrEmpty(valueDisplayControlClientId))
				RenderDisplayControlInitScript(script);
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="properties">A <see cref="PropertyCollection"/> object that will become the jQuery UI initialization options.</param>
		protected override void AddInitProperties(PropertyCollection properties) {
			base.AddInitProperties(properties);

			SliderFixedRange range = Range;
			if(range != defaultRange) properties.AddEnumValue("range", range);

			int value = Value;
			if(value != defaultValue) properties.AddValue("value", value);

			ScriptBuilder slideScript = properties.CreateChildScript();
			RenderSlideScript(slideScript);
			if(slideScript.Length != 0) properties.AddScript("slide", slideScript);

		}

		/// <summary>
		/// Renders the client "slide" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderSlideScript(ScriptBuilder script) {

			script.Append("function(event, ui) {");

			script.Indent++;

			script.NewLine();
			script.RenderIdSelector(ValueClientId);
			script.Append(".val(ui.value);");

			if(!string.IsNullOrEmpty(valueDisplayControlClientId)) {
				script.NewLine();
				script.RenderIdSelector(valueDisplayControlClientId, ":input");
				script.Append(".val(ui.value);");
				script.NewLine();
				script.RenderIdSelector(valueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.value);");
			}

			script.Indent--;
			script.NewLine();

			script.Append("}");

		}

		/// <summary>
		/// Renders the script for initially sychronizing the value of the <see cref="ValueDisplayControl"/>.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderDisplayControlInitScript(ScriptBuilder script) {

			string clientId = ClientID;
			string displayClientId = valueDisplayControlClientId;

			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".val(", displayClientId, ":input", clientId);

			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".text(", displayClientId, ":not(:input)", clientId);
		}

		#endregion


		#region Methods: Routines

		private static void RenderDisplayFieldInitializeScript(ScriptBuilder script, string clientFunction, string displayClientId, string displaySelectorSuffix, string clientId) {
			script.RenderIdSelector(displayClientId, displaySelectorSuffix);
			script.Append(clientFunction);
			script.RenderIdSelector(clientId);
			script.Append(".slider(");
			script.AppendValue("value");
			script.Append("));");
		}

		#endregion


		#region Methods: Post data

		/// <summary>
		/// Processes postback data for an ASP.NET server control.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="postDataKey">The key identifier for the control.</param>
		/// <param name="postCollection">The collection of all incoming name values.</param>
		/// <returns>True if the server control's state changes as a result of the postback; otherwise, false.</returns>
		protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection) {

			Page.ClientScript.ValidateEvent(postDataKey);

			int newValue;
			if(int.TryParse(postCollection[ValueClientId], out newValue) && newValue != Value) {
				Value = newValue;
				return true;
			}

			return false;
		}

		/// <summary>
		/// Signals the server control to notify the ASP.NET application that the state of the control has changed.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void RaisePostDataChangedEvent() {
			OnValueChanged(EventArgs.Empty);
		}

		#endregion


	}

}